from flask import Flask, render_template, request, redirect, url_for, flash, session
from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user
import mysql.connector
from mysql.connector import Error
import datetime
import hashlib
import os
import pickle
from rediscluster import RedisCluster
from rediscluster.exceptions import RedisClusterException

app = Flask(__name__)
app.config['SESSION_COOKIE_NAME'] = 'student_score_session'
app.secret_key = os.urandom(24)


# ----------------------
# 1. 基础配置（修改为你的环境）
# ----------------------
DB_CONFIG = {
    'host': '192.168.1.30',
    'database': 'student_scores',
    'user': 'student_scores',
    'password': 'BJyJjP33XNHC2ZdX'  # 替换为你的MySQL密码
}
REDIS_CLUSTER_NODES = [
    {"host": "192.168.1.40", "port": "6379"},  # 替换为你的Redis集>群节点
    {"host": "192.168.1.41", "port": "6379"},
    {"host": "192.168.1.42", "port": "6379"}
]
REDIS_PASSWORD = "admin1234568"  # 你的Redis密码
SESSION_EXPIRE = 3600


# ----------------------
# 2. Redis Cluster 连接
# ----------------------
def get_redis_cluster():
    try:
        return RedisCluster(
            startup_nodes=REDIS_CLUSTER_NODES,
            password=REDIS_PASSWORD,
            decode_responses=False,
            skip_full_coverage_check=True
        )
    except RedisClusterException as e:
        print(f"Redis Cluster 连接失败: {e}")
        return None

redis_cluster = get_redis_cluster()
if redis_cluster:
    print("Redis Cluster 连接成功（会话存储就绪）")
else:
    print("Redis Cluster 连接失败，程序可能无法保存会话！")


# ----------------------
# 3. 自定义会话接口（添加is_null_session方法）
# ----------------------
class RedisClusterSessionInterface:
    def open_session(self, app, request):
        cookie_name = app.config.get('SESSION_COOKIE_NAME', 'default_session')
        session_id = request.cookies.get(cookie_name)
        
        if not session_id:
            session_id = os.urandom(32).hex()
            return {'session_id': session_id, 'user_id': None}
        
        try:
            session_key = f"session:{session_id}"
            session_data = redis_cluster.get(session_key)
            if session_data:
                return pickle.loads(session_data)
            return {'session_id': session_id, 'user_id': None}
        except Exception as e:
            print(f"读取会话失败: {e}")
            return {'session_id': os.urandom(32).hex(), 'user_id': None}

    def save_session(self, app, session, response):
        if not redis_cluster:
            return
        
        cookie_name = app.config.get('SESSION_COOKIE_NAME', 'default_session')
        session_id = session.get('session_id')
        if not session_id:
            return
        
        try:
            session_data = pickle.dumps(session)
            session_key = f"session:{session_id}"
            redis_cluster.setex(
                name=session_key,
                time=SESSION_EXPIRE,
                value=session_data
            )
            response.set_cookie(
                key=cookie_name,
                value=session_id,
                max_age=SESSION_EXPIRE,
                httponly=True,
                secure=app.config.get('SESSION_COOKIE_SECURE', False),
                path='/'
            )
        except Exception as e:
            print(f"保存会话失败: {e}")

    # 新增：检查是否为空会话（解决AttributeError）
    def is_null_session(self, session):
        return not session or len(session) == 0 or (len(session) == 1 and 'session_id' in session)

app.session_interface = RedisClusterSessionInterface()


# ----------------------
# 4. Flask-Login 配置
# ----------------------
login_manager = LoginManager(app)
login_manager.login_view = 'login'
login_manager.login_message = '请先登录后再访问'

class User(UserMixin):
    def __init__(self, user_id, username):
        self.id = user_id
        self.username = username

@login_manager.user_loader
def load_user(user_id):
    if not user_id or not isinstance(user_id, str):
        return None
    
    connection = create_connection()
    if connection:
        try:
            cursor = connection.cursor(dictionary=True)
            cursor.execute("SELECT id, username FROM users WHERE id = %s", (user_id,))
            user = cursor.fetchone()
            if user:
                return User(str(user['id']), user['username'])
        except Error as e:
            print(f"加载用户失败: {e}")
        finally:
            if connection.is_connected():
                cursor.close()
                connection.close()
    return None


# ----------------------
# 5. 数据库工具函数
# ----------------------
def create_connection():
    connection = None
    try:
        connection = mysql.connector.connect(**DB_CONFIG)
        if connection.is_connected():
            return connection
    except Error as e:
        print(f"MySQL 连接错误: {e}")
    return connection

def init_database():
    connection = create_connection()
    if connection:
        try:
            cursor = connection.cursor()
            cursor.execute("""
            CREATE TABLE IF NOT EXISTS users (
                id INT AUTO_INCREMENT PRIMARY KEY,
                username VARCHAR(50) NOT NULL UNIQUE,
                password VARCHAR(255) NOT NULL,
                created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
            )
            """)
            cursor.execute("""
            CREATE TABLE IF NOT EXISTS students (
                id INT AUTO_INCREMENT PRIMARY KEY,
                name VARCHAR(100) NOT NULL,
                score DECIMAL(5, 2) NOT NULL CHECK (score BETWEEN 0 AND 100),
                created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
                user_id INT NOT NULL,
                FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
            )
            """)
            connection.commit()
            print("数据库表初始化成功（用户表 + 成绩表）")
        except Error as e:
            print(f"初始化数据库错误: {e}")
        finally:
            if connection.is_connected():
                cursor.close()
                connection.close()

def hash_password(password):
    return hashlib.sha256(password.encode('utf-8')).hexdigest()


# ----------------------
# 6. 核心路由
# ----------------------
@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form['username'].strip()
        password = request.form['password'].strip()
        
        if not username or not password:
            flash('用户名和密码不能为空')
            return render_template('login.html')
        
        connection = create_connection()
        if connection:
            try:
                cursor = connection.cursor(dictionary=True)
                cursor.execute("SELECT id, username, password FROM users WHERE username = %s", (username,))
                user = cursor.fetchone()
                
                if user and user['password'] == hash_password(password):
                    user_obj = User(str(user['id']), user['username'])
                    login_user(user_obj, remember=True)
                    return redirect(url_for('index'))
                else:
                    flash('用户名或密码错误')
            except Error as e:
                print(f"登录错误: {e}")
                flash('登录失败，请重试')
            finally:
                if connection.is_connected():
                    cursor.close()
                    connection.close()
        else:
            flash('数据库连接失败')
    
    return render_template('login.html')

@app.route('/register', methods=['GET', 'POST'])
def register():
    if request.method == 'POST':
        username = request.form['username'].strip()
        password = request.form['password'].strip()
        confirm_pwd = request.form['confirm_password'].strip()
        
        if password != confirm_pwd:
            flash('两次密码输入不一致')
            return render_template('register.html')
        if len(username) < 3 or len(password) < 6:
            flash('用户名至少3位，密码至少6位')
            return render_template('register.html')
        
        connection = create_connection()
        if connection:
            try:
                cursor = connection.cursor()
                cursor.execute("SELECT id FROM users WHERE username = %s", (username,))
                if cursor.fetchone():
                    flash('用户名已被占用')
                    return render_template('register.html')
                
                cursor.execute("""
                INSERT INTO users (username, password, created_at)
                VALUES (%s, %s, CURRENT_TIMESTAMP)
                """, (username, hash_password(password)))
                connection.commit()
                flash('注册成功，请登录')
                return redirect(url_for('login'))
            except Error as e:
                print(f"注册错误: {e}")
                flash('注册失败，请重试')
            finally:
                if connection.is_connected():
                    cursor.close()
                    connection.close()
        else:
            flash('数据库连接失败')
    
    return render_template('register.html')

@app.route('/logout')
@login_required
def logout():
    if redis_cluster and 'session_id' in session:
        try:
            session_key = f"session:{session['session_id']}"
            redis_cluster.delete(session_key)
            print("会话已从 Redis 中删除")
        except Exception as e:
            print(f"删除会话失败: {e}")
    
    logout_user()
    cookie_name = app.config.get('SESSION_COOKIE_NAME', 'default_session')
    response = redirect(url_for('login'))
    response.set_cookie(cookie_name, '', expires=0, path='/')
    return response

@app.route('/')
@login_required
def index():
    return render_template('index.html', username=current_user.username)

@app.route('/submit', methods=['POST'])
@login_required
def submit():
    name = request.form['name'].strip()
    score_str = request.form['score'].strip()
    
    if not name:
        return render_template('index.html', error='请输入学生姓名')
    try:
        score = float(score_str)
        if score < 0 or score > 100:
            return render_template('index.html', error='成绩必须在 0-100 之间')
    except ValueError:
        return render_template('index.html', error='成绩必须是有效的数字')
    
    connection = create_connection()
    if connection:
        try:
            cursor = connection.cursor()
            cursor.execute("""
            INSERT INTO students (name, score, user_id)
            VALUES (%s, %s, %s)
            """, (name, score, current_user.id))
            connection.commit()
            return render_template('index.html', success='成绩提交成功！')
        except Error as e:
            print(f"保存成绩错误: {e}")
            return render_template('index.html', error='保存成绩失败，请重试')
        finally:
            if connection.is_connected():
                cursor.close()
                connection.close()
    else:
        return render_template('index.html', error='数据库连接失败')

@app.route('/view')
@login_required
def view_scores():
    students = []
    connection = create_connection()
    if connection:
        try:
            cursor = connection.cursor(dictionary=True)
            cursor.execute("""
            SELECT id, name, score, created_at 
            FROM students 
            WHERE user_id = %s 
            ORDER BY created_at DESC
            """, (current_user.id,))
            students = cursor.fetchall()
            for student in students:
                if isinstance(student['created_at'], datetime.datetime):
                    student['created_at'] = student['created_at'].strftime('%Y-%m-%d %H:%M:%S')
        except Error as e:
            print(f"查询成绩错误: {e}")
            flash('查询成绩失败，请重试')
        finally:
            if connection.is_connected():
                cursor.close()
                connection.close()
    return render_template('view.html', students=students)

@app.route('/delete/<int:student_id>')
@login_required
def delete_student(student_id):
    connection = create_connection()
    if connection:
        try:
            cursor = connection.cursor()
            cursor.execute("""
            DELETE FROM students 
            WHERE id = %s AND user_id = %s
            """, (student_id, current_user.id))
            connection.commit()
            if cursor.rowcount > 0:
                flash('成绩删除成功')
            else:
                flash('无法删除：该成绩不存在或不属于你')
        except Error as e:
            print(f"删除成绩错误: {e}")
            flash('删除成绩失败，请重试')
        finally:
            if connection.is_connected():
                cursor.close()
                connection.close()
    else:
        flash('数据库连接失败')
    return redirect(url_for('view_scores'))


# ----------------------
# 7. 程序启动
# ----------------------
if __name__ == '__main__':
    init_database()
    app.run(host='0.0.0.0', port=5000, debug=True)

